unit Main;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, StdCtrls, ComCtrls, IniFiles, Menus, Buttons, MPlayer,
  ActionButtonUnit, KeyboardUnit;

type
  TPerspectiveItem = class
  public
    HotKey: TShortCut;
    Center: Double;
    CenterY: Integer;
  end;

  TMainForm = class(TForm)
    Timer1: TTimer;
    SetCenterHotKey: THotKey;
    Label1: TLabel;
    MoveKey: THotKey;
    Label2: TLabel;
    HotKeyUp: THotKey;
    HotKeyLeft: THotKey;
    HotKeyDown: THotKey;
    HotKeyRight: THotKey;
    btnSave: TButton;
    Label3: TLabel;
    PerspectiveHotKey: THotKey;
    PerspectiveList: TListBox;
    Bevel1: TBevel;
    hkMoveStyle0: THotKey;
    imgUp: TImage;
    hkMoveStyle1: THotKey;
    hkMoveStyle2: THotKey;
    hkMoveStyle3: THotKey;
    rbMoveStyle0: TRadioButton;
    rbMoveStyle1: TRadioButton;
    rbMoveStyle2: TRadioButton;
    rbMoveStyle3: TRadioButton;
    grp1: TGroupBox;
    imgLeft: TImage;
    imgDown: TImage;
    imgRight: TImage;
    procedure Timer1Timer(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure HotKeyUpChange(Sender: TObject);
    procedure btnSaveClick(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure PerspectiveListClick(Sender: TObject);
    procedure PerspectiveHotKeyChange(Sender: TObject);
    procedure rbMoveStyle0Click(Sender: TObject);
    procedure rbMoveStyle1Click(Sender: TObject);
    procedure rbMoveStyle2Click(Sender: TObject);
    procedure rbMoveStyle3Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    hDiabloWnd: THandle;
    dx: Integer;
    dy: Integer;
    offsetY: Integer;
    ini: TIniFile;
    _up, _down, _left, _right: Integer;
    saveCursorPos: TPoint;
    activePerspective: TPerspectiveItem;
    actionButtons: TList;
    moveStyle: Integer;
    toggleShift: Boolean;

    mouseEvents: TMouseEvents;
  end;

var
  MainForm: TMainForm;

implementation

{$R *.dfm}

procedure TMainForm.btnSaveClick(Sender: TObject);
var
  i: Integer;
  S: String;
  pItem: TPerspectiveItem;
  actionButton: TActionButton;
begin
  DecimalSeparator := '.';

  ini.WriteInteger('Main', 'offsetY', offsetY);
  ini.WriteInteger('Main', 'dx', dx);
  ini.WriteInteger('Main', 'dy', dy);
  ini.WriteInteger('Main', 'timeInterval', Timer1.Interval);
  ini.WriteInteger('Main', 'setCenter', SetCenterHotKey.HotKey);

  ini.WriteInteger('Main', 'move', MoveKey.HotKey);

  for i := 0 to PerspectiveList.Count - 1 do begin
    pItem := PerspectiveList.Items.Objects[i] as TPerspectiveItem;
    S := 'perspective' + IntToStr(i + 1);
    ini.WriteInteger('Main', S + 'HotKey', pItem.HotKey);
    ini.WriteFloat('Main', S + 'Center', pItem.Center);
  end;
  ini.WriteInteger('Main', 'activePerspective', PerspectiveList.ItemIndex + 1);
  {
  for i := 0 to actionButtons.Count - 1 do begin
    actionButton := TActionButton(actionButtons[i]);
    S := 'actionButton' + IntToStr(i + 1);
    ini.WriteString('Main', S + 'SoundFile', actionButton.FileName);
    ini.WriteInteger('Main', S + 'HotKey1', actionButton.HotKey1.HotKey);
    ini.WriteInteger('Main', S + 'HotKey2', actionButton.HotKey2.HotKey);
    ini.WriteInteger('Main', S + 'Cooldown', actionButton.CooldownInterval);
  end;
  }
  ini.WriteInteger('Main', 'up', _up);
  ini.WriteInteger('Main', 'down', _down);
  ini.WriteInteger('Main', 'left', _left);
  ini.WriteInteger('Main', 'right', _right);

  ini.WriteInteger('Main', 'moveStyle0', hkMoveStyle0.HotKey);
  ini.WriteInteger('Main', 'moveStyle1', hkMoveStyle1.HotKey);
  ini.WriteInteger('Main', 'moveStyle2', hkMoveStyle2.HotKey);
  ini.WriteInteger('Main', 'moveStyle3', hkMoveStyle3.HotKey);
  ini.WriteInteger('Main', 'moveStyleSelected', moveStyle);
end;

procedure TMainForm.FormCreate(Sender: TObject);

  procedure ReadPerspective(Index: Integer; DefaultHotKey: TShortCut; DefaultCenter: Extended);
  var
    pItem: TPerspectiveItem;
    S, S2: String;
  begin
    S := 'perspective' + IntToStr(Index);
    pItem := TPerspectiveItem.Create;
    pItem.HotKey := ini.ReadInteger('Main', S + 'HotKey', DefaultHotKey);
    S2 := FloatToStr(DefaultCenter);
    S2 := ini.ReadString('Main', S + 'Center', S2);
    pItem.Center := StrToFloat(S2);
    PerspectiveList.AddItem(ShortCutToText(pItem.HotKey), pItem);
  end;

  function CreateActionButton(aLeft: Integer; Index: Integer; aHotKey1: TShortCut; aSoundFile: String): TActionButton;
  var
    S: String;
  begin
    S := 'actionButton' + IntToStr(Index);
    Result := TActionButton.Create(Self);
    Result.Name := S;
    Result.Parent := Self;
    Result.Left := aLeft;
    Result.Top := Bevel1.BoundsRect.Bottom + 5;
    //Result.FileName := ini.ReadString('Main', S + 'SoundFile', aSoundFile);
    Result.FileName := aSoundFile;
    Result.HotKey1.HotKey := ini.ReadInteger('Main', S + 'HotKey1', aHotKey1);
    Result.HotKey2.HotKey := ini.ReadInteger('Main', S + 'HotKey2', 0);
    Result.cooldownInterval := ini.ReadInteger('Main', S + 'Cooldown', 15);
    actionButtons.Add(Result);
  end;

var
  i: Integer;
  actionButton: TActionButton;
begin
  mouseEvents := TMouseEvents.Create(Self);
  mouseEvents.Parent := Self;
  mouseEvents.Open;

  DecimalSeparator := '.';
  ini := TIniFile.Create(ExtractFilePath(Application.ExeName) + 'config.ini');

  {
  actionButtons := TList.Create;
  actionButton := CreateActionButton(Bevel1.Left, 1, Ord('1'), 'Sounds\1.wav');
  actionButton := CreateActionButton(actionButton.Right + 5, 2, Ord('2'), 'Sounds\2.wav');
  actionButton := CreateActionButton(actionButton.Right + 5, 3, Ord('3'), 'Sounds\3.wav');
  actionButton := CreateActionButton(actionButton.Right + 5, 4, Ord('4'), 'Sounds\4.wav');
  actionButton := CreateActionButton(actionButton.Right + 5, 5, VK_LBUTTON, 'Sounds\5.wav');
  actionButton := CreateActionButton(actionButton.Right + 5, 6, VK_RBUTTON, 'Sounds\6.wav');
  actionButton.HotKey1.HotKey := VK_RBUTTON;

  Width := actionButton.Right + Bevel1.Left;
  }

  offsetY := ini.ReadInteger('Main', 'offsetY', -34);
  dx := ini.ReadInteger('Main', 'dx', 40);
  dy := ini.ReadInteger('Main', 'dy', 27);
  Timer1.Interval := ini.ReadInteger('Main', 'timeInterval', 50);

  SetCenterHotKey.HotKey := ini.ReadInteger('Main', 'setCenter', VK_F8);

  MoveKey.HotKey := ini.ReadInteger('Main', 'move', VK_F12);

  PerspectiveList.Items.Clear;
  ReadPerspective(1, VK_F5, 0.47037037037037);
  ReadPerspective(2, VK_F6, 0.594444444444444);
  ReadPerspective(3, VK_F7, 0.524074074074074);
  i := 4;
  while ini.ValueExists('Main', 'perspective' + IntToStr(i) + 'HotKey') do begin
    ReadPerspective(i, VK_NONAME, 0.881533101);
    i := i + 1;
  end;
  i := ini.ReadInteger('Main', 'activePerspective', 1);
  if (i >= 0) and (i < PerspectiveList.Items.Count) then begin
    PerspectiveList.ItemIndex := i - 1;
    PerspectiveListClick(nil);
  end else begin
    PerspectiveList.ItemIndex := 0;
    PerspectiveListClick(nil);
  end;


  HotKeyUp.HotKey := ini.ReadInteger('Main', 'up', ord('W'));
  HotKeyDown.HotKey := ini.ReadInteger('Main', 'down', ord('S'));
  HotKeyLeft.HotKey := ini.ReadInteger('Main', 'left', ord('A'));
  HotKeyRight.HotKey := ini.ReadInteger('Main', 'right', ord('D'));
  HotKeyUpChange(nil);

  hkMoveStyle0.HotKey := ini.ReadInteger('Main', 'moveStyle0', Ord('1') or scCtrl);
  hkMoveStyle1.HotKey := ini.ReadInteger('Main', 'moveStyle1', Ord('2') or scCtrl);
  hkMoveStyle2.HotKey := ini.ReadInteger('Main', 'moveStyle2', Ord('3') or scCtrl);
  hkMoveStyle3.HotKey := ini.ReadInteger('Main', 'moveStyle3', Ord('4') or scCtrl);

  moveStyle := ini.ReadInteger('Main', 'moveStyleSelected', 2);
  case moveStyle of
    0: rbMoveStyle0.Checked := True;
    1: rbMoveStyle1.Checked := True;
    2: rbMoveStyle2.Checked := True;
    3: rbMoveStyle3.Checked := True;
  end;

  toggleShift := False;
end;

procedure TMainForm.FormDestroy(Sender: TObject);
begin
  if toggleShift then
    keybd_event(VK_SHIFT, 0, KEYEVENTF_KEYUP, 0);

  btnSaveClick(nil);
  ini.Free;
end;

procedure TMainForm.HotKeyUpChange(Sender: TObject);
begin
  _up := HotKeyUp.HotKey;
  _down := HotKeyDown.HotKey;
  _left := HotKeyLeft.HotKey;
  _right := HotKeyRight.HotKey;
end;

procedure TMainForm.PerspectiveHotKeyChange(Sender: TObject);
var
  i: Integer;
  pItem: TPerspectiveItem;
begin
  i := PerspectiveList.ItemIndex;
  if (i >= 0) and (i < PerspectiveList.Count) then begin
    pItem := PerspectiveList.Items.Objects[i] as TPerspectiveItem;
    pItem.HotKey := PerspectiveHotKey.HotKey;
    PerspectiveList.Items[i] := ShortCutToText(pItem.HotKey);
  end;
end;

procedure TMainForm.PerspectiveListClick(Sender: TObject);
var
  i: Integer;
begin
  i := PerspectiveList.ItemIndex;
  if (i >= 0) and (i < PerspectiveList.Count) then begin
    activePerspective := PerspectiveList.Items.Objects[i] as TPerspectiveItem;
    PerspectiveHotKey.Enabled := True;
    PerspectiveHotKey.HotKey := activePerspective.HotKey;
  end else
    PerspectiveHotKey.Enabled := True;
end;

procedure TMainForm.rbMoveStyle0Click(Sender: TObject);
begin
  moveStyle := 0;
end;

procedure TMainForm.rbMoveStyle1Click(Sender: TObject);
begin
  moveStyle := 1;
end;

procedure TMainForm.rbMoveStyle2Click(Sender: TObject);
begin
  moveStyle := 2;
end;

procedure TMainForm.rbMoveStyle3Click(Sender: TObject);
begin
  moveStyle := 3;
end;

procedure TMainForm.Timer1Timer(Sender: TObject);
var
  oldPos: TPoint;
  i: Integer;
  x, y: Integer;
  wndRect: TRect;
  w, h: Integer;
  pItem: TPerspectiveItem;
  actionButton: TActionButton;
  eu: array [0..3] of TInput;
  oldAccel, newAccel: array[0..2] of Integer;
begin
  if not IsWindow(hDiabloWnd) then
    hDiabloWnd := FindWindow('D3 Main Window Class', nil);

  if hDiabloWnd = GetForegroundWindow then begin

    UpdateKeyboardState;

    if IsShortCutDown(hkMoveStyle0.HotKey) then begin
      rbMoveStyle0.Checked := True;
    end else
    if IsShortCutDown(hkMoveStyle1.HotKey) then begin
      rbMoveStyle1.Checked := True;
    end else
    if IsShortCutDown(hkMoveStyle2.HotKey) then begin
      rbMoveStyle2.Checked := True;
    end else
    if IsShortCutDown(hkMoveStyle3.HotKey) then begin
      rbMoveStyle3.Checked := True;
    end;

    for i := PerspectiveList.Items.Count - 1 downto 0 do begin
      pItem := TPerspectiveItem(PerspectiveList.Items.Objects[i]);
      if IsShortCutDown(pItem.HotKey) then begin
        PerspectiveList.ItemIndex := i;
        PerspectiveListClick(nil);
        break;
      end;
    end;

    GetWindowRect(hDiabloWnd, wndRect);
    w := wndRect.Right - wndRect.Left;
    h := wndRect.Bottom - wndRect.Top;
    activePerspective.CenterY := Round(wndRect.Top + h * activePerspective.Center);

    if IsShortCutDown(SetCenterHotKey.HotKey) then begin
      GetCursorPos(oldPos);
      offsetY := oldPos.Y - wndRect.Top;
      activePerspective.Center := offsetY / h;
    end;

    {
    for i := 0 to actionButtons.Count - 1 do begin
      //actionButton := TActionButton(actionButtons[i]);
      //actionButton.UpdateButton;
    end;
    //PlayQueue.Update;
    }

    if mouseEvents.SkipListCount > 0 then Exit;

    GetCursorPos(oldPos);
    x := wndRect.Left + w div 2;
    y := activePerspective.CenterY;

    if IsKeyPressed(_up) or IsKeyPressed(_down) or IsKeyPressed(_left) or IsKeyPressed(_right) then begin

      // W
      if IsKeyPressed(_up) then begin
        y := y - dy;
      end;
      // S
      if IsKeyPressed(_down) then begin
        y := y + dy;
      end;
      // A
      if IsKeyPressed(_left) then begin
        x := x - dx;
      end;
      // D
      if IsKeyPressed(_right) then begin
        x := x + dx;
      end;

      if IsKeyPressed(_left) and IsKeyPressed(_right) then begin
        keybd_event(VK_SHIFT, 0, 0, 0);
        toggleShift := True;
        x := oldPos.X; // dont click near player
        y := oldPos.Y + 1; // but not same point, that have cursor
      end else
      if toggleShift then begin
        keybd_event(VK_SHIFT, 0, KEYEVENTF_KEYUP, 0);
        toggleShift := False;
      end;

      //mouseEvents.Lock := True;

      ZeroMemory(@eu,sizeof(eu));

      SystemParametersInfo(SPI_GETMOUSE, 0, @oldAccel[0], 0);
      newAccel := oldAccel;
      newAccel[2] := 0;
      SystemParametersInfo(SPI_SETMOUSE, 0, @newAccel[0], SPIF_SENDCHANGE);

      if toggleShift then begin
        case moveStyle of
          1: begin
          end;
          2: begin
            //mouseEvents.AddSkipMessage(WM_MOUSEMOVE);
            if IsKeyPressed(VK_LBUTTON) then begin
              mouseEvents.AddSkipMessage(WM_LBUTTONDOWN);
              eu[0].Itype := INPUT_MOUSE;
              eu[0].mi.dwFlags := MOUSEEVENTF_LEFTDOWN;

              mouseEvents.AddSkipMessage(WM_LBUTTONUP);
              eu[1].Itype := INPUT_MOUSE;
              eu[1].mi.dwFlags := MOUSEEVENTF_LEFTUP;

              SendInput(2, eu[0], sizeof(TInput));
            end else begin
            end;
          end;
          3: begin
            //mouseEvents.AddSkipMessage(WM_MOUSEMOVE);
            mouseEvents.AddSkipMessage(WM_LBUTTONDOWN);
            eu[0].Itype := INPUT_MOUSE;
            eu[0].mi.dwFlags := MOUSEEVENTF_LEFTDOWN;

            //mouseEvents.AddSkipMessage(WM_MOUSEMOVE);
            mouseEvents.AddSkipMessage(WM_LBUTTONUP);
            eu[1].Itype := INPUT_MOUSE;
            eu[1].mi.dwFlags := MOUSEEVENTF_LEFTUP;

            SendInput(2, eu[0], sizeof(TInput));
          end;
          else begin
            // wasd off
          end;
        end;
      end else begin
        case moveStyle of
          1: begin
            //mouseEvents.AddSkipMessage(WM_MOUSEMOVE);
            eu[0].Itype := INPUT_MOUSE;
            eu[0].mi.dwFlags := MOUSEEVENTF_MOVE;
            eu[0].mi.dx := x - oldPos.X;
            eu[0].mi.dy := y - oldPos.Y;

            eu[1].Itype := INPUT_KEYBOARD;
            eu[1].ki.wVk := VK_F12;

            eu[2].Itype := INPUT_KEYBOARD;
            eu[2].ki.wVK := VK_F12;
            eu[2].ki.dwFlags := KEYEVENTF_KEYUP;

            //mouseEvents.AddSkipMessage(WM_MOUSEMOVE);
            eu[3].Itype := INPUT_MOUSE;
            eu[3].mi.dwFlags := MOUSEEVENTF_MOVE;
            eu[3].mi.dx := oldPos.X - x;
            eu[3].mi.dy := oldPos.Y - y;
            mouseEvents.waitPoint.X := eu[3].mi.dx;
            mouseEvents.waitPoint.Y := eu[3].mi.dy;

            SendInput(4, eu[0], sizeof(TInput));
          end;
          2: begin
            //mouseEvents.AddSkipMessage(WM_MOUSEMOVE);
            eu[0].Itype := INPUT_MOUSE;
            eu[0].mi.dwFlags := MOUSEEVENTF_MOVE;
            eu[0].mi.dx := x - oldPos.X;
            eu[0].mi.dy := y - oldPos.Y;

            if IsKeyPressed(VK_LBUTTON) then begin
              mouseEvents.AddSkipMessage(WM_LBUTTONDOWN);
              eu[1].Itype := INPUT_MOUSE;
              eu[1].mi.dwFlags := MOUSEEVENTF_LEFTDOWN;

              mouseEvents.AddSkipMessage(WM_LBUTTONUP);
              eu[2].Itype := INPUT_MOUSE;
              eu[2].mi.dwFlags := MOUSEEVENTF_LEFTUP;
            end else begin
              eu[1].Itype := INPUT_KEYBOARD;
              eu[1].ki.wVk := VK_F12;

              eu[2].Itype := INPUT_KEYBOARD;
              eu[2].ki.wVK := VK_F12;
              eu[2].ki.dwFlags := KEYEVENTF_KEYUP;
            end;

            //mouseEvents.AddSkipMessage(WM_MOUSEMOVE);
            eu[3].Itype := INPUT_MOUSE;
            eu[3].mi.dwFlags := MOUSEEVENTF_MOVE;
            eu[3].mi.dx := oldPos.X - x;
            eu[3].mi.dy := oldPos.Y - y;
            mouseEvents.waitPoint.X := eu[3].mi.dx;
            mouseEvents.waitPoint.Y := eu[3].mi.dy;

            SendInput(4, eu[0], sizeof(TInput));
          end;
          3: begin
            //mouseEvents.AddSkipMessage(WM_MOUSEMOVE);
            mouseEvents.AddSkipMessage(WM_LBUTTONDOWN);
            eu[0].Itype := INPUT_MOUSE;
            eu[0].mi.dwFlags := MOUSEEVENTF_MOVE or MOUSEEVENTF_LEFTDOWN;
            eu[0].mi.dx := x - oldPos.X;
            eu[0].mi.dy := y - oldPos.Y;

            //mouseEvents.AddSkipMessage(WM_MOUSEMOVE);
            mouseEvents.AddSkipMessage(WM_LBUTTONUP);
            eu[1].Itype := INPUT_MOUSE;
            eu[1].mi.dwFlags := MOUSEEVENTF_LEFTUP or MOUSEEVENTF_MOVE;
            eu[1].mi.dx := oldPos.X - x;
            eu[1].mi.dy := oldPos.Y - y;
            mouseEvents.waitPoint.X := eu[1].mi.dx;
            mouseEvents.waitPoint.Y := eu[1].mi.dy;

            SendInput(2, eu[0], sizeof(TInput));
          end;
          else begin
            // wasd off
          end;
        end;
      end;
      SystemParametersInfo(SPI_SETMOUSE, 0, @oldAccel[0], SPIF_SENDCHANGE);
      //mouseEvents.Lock := False;
    end else
      if IsKeyPressed(VK_LBUTTON) and (IsKeyUp(_up) or IsKeyUp(_down) or IsKeyUp(_left) or IsKeyUp(_right)) then
        mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
  end else
    if toggleShift then begin
      keybd_event(VK_SHIFT, 0, KEYEVENTF_KEYUP, 0);
      toggleShift := False;
    end;
end;

end.
